package com.joysuch.wwyt.workflow.engine;

import cn.hutool.core.util.IdUtil;
import cn.zlg.common.util.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.common.constant.Constant;
import com.joysuch.wwyt.core.constant.Constants;
import com.joysuch.wwyt.core.enums.Status;
import com.joysuch.wwyt.core.exception.ApiBusinessException;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.workflow.bean.*;
import com.joysuch.wwyt.workflow.entity.WorkFlowProcess;
import com.joysuch.wwyt.workflow.exception.WorkFlowException;
import com.joysuch.wwyt.workflow.repository.WorkFlowProcessDao;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.editor.constants.EditorJsonConstants;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.constants.StencilConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.activiti.editor.constants.ModelDataJsonConstants.*;

/**
 * @author qingzhou
 * 2020-02-07
 */
@Slf4j
@Service
@Transactional(rollbackFor = {Exception.class})
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ProcessAPI {

    private final ObjectMapper objectMapper;
    private final RepositoryService repositoryService;


    @Autowired
    private WorkFlowProcessDao workFlowProcessDao;

    public Model newModel(String modelName, String modelKey, String description) {
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

        RepositoryService repositoryService = processEngine.getRepositoryService();

        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);
        Model modelData = repositoryService.newModel();

        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(MODEL_NAME, modelName);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setName(modelName);
        modelData.setKey(modelKey);

        //保存模型
        repositoryService.saveModel(modelData);
        try {
            repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            throw new WorkFlowException(e.getMessage());
        }
        log.info("创建新模型 {}", modelData);
        return modelData;
    }

    public String saveModel(WorkFlowEditModelBean modelBean) throws ApiBusinessException {
        try {
            String message = null;
            //解析每个节点配置信息: name, assign, status_name, description
            Map<String, WorkFlowModelJsonBean> jsonBeanMap = new HashMap<>();
            final String modelJson = modelBean.getModelJson();
            if (StringUtils.isNotEmpty(modelJson)) {
                List<WorkFlowModelJsonBean> modelJsonBeanList = JSONArray.parseArray(modelJson, WorkFlowModelJsonBean.class);
                message = checkSameElement(modelJsonBeanList);
                jsonBeanMap = modelJsonBeanList.stream().collect(Collectors.toMap(WorkFlowModelJsonBean::getName, item -> item, (k1, k2) -> k2));
            }
            Model model = findModel(modelBean.getModelId());
            if (model == null) {
                throw new WorkFlowException("要保存的模型不存在");
            }
            ObjectNode metaInfo = (ObjectNode) new ObjectMapper().readTree(model.getMetaInfo());

            metaInfo.put(MODEL_NAME, modelBean.getName());
            metaInfo.put(MODEL_DESCRIPTION, modelBean.getDescription());
            model.setMetaInfo(metaInfo.toString());
            model.setName(modelBean.getName());
            repositoryService.saveModel(model);

            JSONObject jsonXml = JSON.parseObject(modelBean.getJsonXml());
            JSONObject properties = jsonXml.getJSONObject(EditorJsonConstants.EDITOR_SHAPE_PROPERTIES);
            String processId = (String) properties.get(StencilConstants.PROPERTY_PROCESS_ID);
            if (Constants.DEFINITION_KEY.equals(processId) || StringUtils.isBlank(processId)) {
                processId = Constants.DEFINITION_KEY_PREFIX + IdUtil.fastUUID();
                properties.put(StencilConstants.PROPERTY_PROCESS_ID, processId);
                jsonXml.put(EditorJsonConstants.EDITOR_SHAPE_PROPERTIES, properties);
            }

            repositoryService.addModelEditorSource(model.getId(), jsonXml.toJSONString().getBytes(StandardCharsets.UTF_8));
            InputStream svgStream = new ByteArrayInputStream(modelBean.getSvgXml().getBytes(StandardCharsets.UTF_8));
            TranscoderInput input = new TranscoderInput(svgStream);

            PNGTranscoder transcoder = new PNGTranscoder();
            // Setup output
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            TranscoderOutput output = new TranscoderOutput(outStream);

            // Do the transformation
            transcoder.transcode(input, output);
            final byte[] result = outStream.toByteArray();
            repositoryService.addModelEditorSourceExtra(model.getId(), result);
            outStream.close();

            //保存流程相关的信息
            if (modelBean.getJsonXml() == null || StringUtils.isEmpty(modelBean.getJsonXml())) {
                return null;
            }
            ObjectNode objectNode = (ObjectNode) new ObjectMapper().readTree(modelBean.getJsonXml());
            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(objectNode);
            Process process = bpmnModel.getProcesses().get(0);

            WorkFlowProcess entity = workFlowProcessDao.findByModelId(modelBean.getModelId());
            //设置 process 定义ID
            entity.setDefinitionKey(processId);
            entity.setModelJson(modelJson);
            Collection<FlowElement> flowElements = process.getFlowElements();

            List<FlowElement> taskList = flowElements.stream().filter(item -> (item instanceof UserTask)).collect(Collectors.toList());
            Map<String, FlowElement> flowElementMap = flowElements.stream().collect(Collectors.toMap(FlowElement::getId, Function.identity()));
            Map<String, String> flowMap = flowElements.stream().collect(Collectors.toMap(FlowElement::getId, FlowElement::getName));

            List<FlowElement> startElements = flowElements.stream().filter(item -> (item instanceof StartEvent)).collect(Collectors.toList());
            if (startElements.size() > 1) {
                throw new ApiBusinessException(Status.START_NODE_MORE_THAN_ONE);
            }
            List<FlowElement> endElements = flowElements.stream().filter(item -> (item instanceof EndEvent)).collect(Collectors.toList());
            FlowElement endElement = null;

            List<WorkFlowTaskNameBean> taskNameList = new ArrayList<>();
            StartEvent startEvent = (StartEvent) startElements.get(0);
            List<SequenceFlow> startEventOutgoingFlows = startEvent.getOutgoingFlows();
            if (startEventOutgoingFlows.size() > 1) {
                throw new ApiBusinessException(Status.PARALLEL_NODES_NOT_CONFIGURED_AFTER_START_NODE);
            }
            for (FlowElement elem : taskList) {
                UserTask flow = (UserTask) elem;
                WorkFlowTaskNameBean taskNameBean = new WorkFlowTaskNameBean();
                taskNameBean.setTaskName(flow.getName());
                taskNameBean.setTaskElementId(flow.getId());
                //设置第一个节点
                if (startEventOutgoingFlows.get(0).getTargetRef().equals(flow.getId())) {
                    taskNameBean.setShowOrder(0);
                } else {
                    taskNameBean.setShowOrder(1);
                }
                WorkFlowModelJsonBean jsonBean = jsonBeanMap.get(flow.getName());
                if (jsonBean != null) {
                    taskNameBean.setRecordHistory(jsonBean.getRecordHistory());
                    taskNameBean.setRiskreeval(jsonBean.getRiskreeval());
                    taskNameBean.setDescription(jsonBean.getDescription());
                    String flowkey = jsonBean.getFlowkey();
                    taskNameBean.setFlowKey(StringUtils.isEmpty(flowkey) ? null : JSONObject.parseObject(flowkey, FlowKeyBean.class));
                    Integer countersign = jsonBean.getCountersign();
                    taskNameBean.setCountersign(countersign);
                    //JSONObject assign =JSONObject.parseObject(jsonBean.getAssign());
//                    if(assign != null){
//                        taskNameBean.setAssignKey(assign.getString("key"));
//                        taskNameBean.setAssignValue(assign.getString("value"));
//                        taskNameBean.setDiaType(assign.getString("diaType"));
//                    }
                    //兼容旧数据
                    JSONObject jsonObject1 = null;
                    JSONArray jsonArray = null;
                    try {
                        jsonObject1 = JSONObject.parseObject(jsonBean.getAssign());
                    } catch (Exception e) {
                    }
                    if (jsonObject1 != null) {
                        taskNameBean.setAssignKey(jsonObject1.getString("key"));
                        taskNameBean.setAssignValue(jsonObject1.getString("value"));
                        taskNameBean.setDiaType(jsonObject1.getString("diaType"));
                    }
                    try {
                        jsonArray = JSONArray.parseArray(jsonBean.getAssign());
                    } catch (Exception e) {
                    }
                    if (jsonArray != null) {
                        if (countersign == null || countersign == 0) {
                            if (jsonArray.size() != 0) {
                                JSONObject jsonObject = (JSONObject) jsonArray.get(0);
                                taskNameBean.setAssignKey(jsonObject.getString("key"));
                                taskNameBean.setAssignValue(jsonObject.getString("value"));
                                taskNameBean.setDiaType(jsonObject.getString("diaType"));
                            }
                        } else if (countersign == 1 || countersign == 2) {
                            List<CountersignBean> countersignBeanList = Lists.newArrayList();
                            for (Object o : jsonArray) {
                                CountersignBean countersignBean = new CountersignBean();
                                JSONObject temp = (JSONObject) o;
                                countersignBean.setAssignKey(temp.getString("key"));
                                countersignBean.setAssignValue(temp.getString("value"));
                                countersignBean.setDiaType(temp.getString("diaType"));
                                countersignBeanList.add(countersignBean);
                            }
                            taskNameBean.setCountersignList(countersignBeanList);
                        }
                    }
                }

                //从当前节点流出的支流
                List<SequenceFlow> outs = flow.getOutgoingFlows();

                if (outs == null || outs.size() == 0) {
                    taskNameList.add(taskNameBean);
                    continue;
                }
                SequenceFlow sequence = outs.get(0);
                FlowElement nextElement = process.getFlowElement(sequence.getTargetRef());
                if (nextElement instanceof UserTask) {
                    taskNameBean.setNextTaskName(nextElement.getName());

                }
                if (nextElement instanceof EndEvent) {
                    taskNameBean.setNextTaskName(nextElement.getName());
                    endElement = nextElement;
                } else if (nextElement instanceof ExclusiveGateway) {
                    //排他网关判断
                    List<SequenceFlow> outWays = ((ExclusiveGateway) nextElement).getOutgoingFlows();
                    String preTaskName = null;
                    String nextTaskName = null;
                    for (SequenceFlow way : outWays) {
                        if (way.getConditionExpression().isEmpty() || way.getConditionExpression().contains("approve==1") || way.getConditionExpression().contains("param==1")) {
                            nextTaskName = flowMap.get(way.getTargetRef());
                        } else {
                            preTaskName = flowMap.get(way.getTargetRef());
                        }
                    }
                    taskNameBean.setNextTaskName(StringUtils.isNotEmpty(nextTaskName) ? nextTaskName : nextElement.getName());
                    taskNameBean.setPreTaskName(preTaskName);
                }
                //从当前节点流进的支流
                List<SequenceFlow> ins = flow.getIncomingFlows();
                if (ins.size() == 1) {
                    taskNameBean.setTaskStatus(ins.get(0).getName());
                } else {
                    Map<String, String> statusMap = new HashMap<>();
                    for (SequenceFlow in : ins) {
                        String sourceRef = in.getSourceRef();
                        FlowElement flowElement = flowElementMap.get(sourceRef);
                        String flowName = flowMap.get(sourceRef);
                        if (flowElement instanceof UserTask) {
                            statusMap.put(flowName, in.getName());
                            taskNameBean.setPreTaskName(flowName);
                        }
                        //遇到网关，需要取网关的前一个节点
                        else if (flowElement instanceof ExclusiveGateway) {
                            List<SequenceFlow> incomingFlows = ((ExclusiveGateway) flowElement).getIncomingFlows();
                            SequenceFlow sequenceFlow = incomingFlows.get(0);
                            String ref = sequenceFlow.getSourceRef();
                            String elementName = flowMap.get(ref);
                            statusMap.put(elementName, in.getName());
                        }
                        //这种方式在多支流(>=3)流入和网关节点流入的情况下都有问题
//                        statusMap.put(flowMap.get(in.getSourceRef()),in.getName());
//                        if(in.getConditionExpression() == null || StringUtils.isEmpty(in.getConditionExpression())|| in.getConditionExpression().contains("param==1")){
//                            statusMap.put("1",in.getName());
//                        }else{
//                            statusMap.put("0",in.getName());
//                        }
                    }
                    taskNameBean.setTaskStatusMap(statusMap);
                }
                taskNameBean.setCompleteStatus(0);
                taskNameList.add(taskNameBean);
            }
            // 添加结束节点

            if (endElements != null && endElements.size() > 0) {
                endElement = endElements.get(0);
                WorkFlowTaskNameBean endTask = new WorkFlowTaskNameBean();
                if (endElement.getName() == null || StringUtils.isEmpty(endElement.getName())) {
                    endTask.setTaskName("完成");
                } else {
                    endTask.setTaskName(endElement.getName());
                }

                endTask.setTaskStatus(endElement.getName());
                endTask.setTaskElementId(endElement.getId());
                endTask.setCompleteStatus(1);
                endTask.setShowOrder(999);
                taskNameList.add(endTask);
            }

            taskNameList.sort(Comparator.comparing(WorkFlowTaskNameBean::getShowOrder));

            entity.setTaskNameList(JSONArray.parseArray(JSONObject.toJSONString(taskNameList)).toJSONString());
            entity.setUpdateBy(Context.getCurrentUserId());
            entity.setUpdateTime(new Date());
            workFlowProcessDao.save(entity);
            return message;
        } catch (TranscoderException | IOException e) {
            log.error("保存模型失败", e);
            throw new WorkFlowException("保存模型失败: " + e.getMessage());
        }
    }

    private String checkSameElement(List<WorkFlowModelJsonBean> modelJsonBeanList) {
        Map<String, List<WorkFlowModelJsonBean>> jsonListGroupMap = modelJsonBeanList.stream().filter(each -> StringUtils.isNotBlank(each.getName())).collect(Collectors.groupingBy(WorkFlowModelJsonBean::getName));
        StringBuilder checkMessage = new StringBuilder();
        for (Map.Entry<String, List<WorkFlowModelJsonBean>> each : jsonListGroupMap.entrySet()) {
            if (CollectionUtils.isNotEmpty(each.getValue()) && each.getValue().size() > 1) {
                if (checkMessage.length() > 0) {
                    checkMessage.append(Constant.SPLIT_STOP).append(each.getKey());
                } else {
                    checkMessage.append(each.getKey());
                }
            }
        }
        return checkMessage.length() > 0 ? String.format(Status.PROCESS_NODE_THE_SAME_NAME_EXISTS.getMsg(), checkMessage) : null;
    }

    public Deployment deploy(String modelId) throws IOException {
        Model model = findModel(modelId);
        if (model == null) {
            throw new WorkFlowException("要部署的模型不存在");
        }
        byte[] modelEditorSource = findModelEditorSource(model.getId());
        if (modelEditorSource == null || modelEditorSource.length == 0) {
            throw new WorkFlowException("要部署的模型源码不存在");
        }

        ObjectNode objectNode = (ObjectNode) new ObjectMapper().readTree(modelEditorSource);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(objectNode);
        //流程内容为空
        if (CollectionUtils.isNotEmpty(bpmnModel.getProcesses())) {
            byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
            String processName = model.getName() + ".bpmn20.xml";
            log.info("部署新模型 {}：", new String(xmlBytes, StandardCharsets.UTF_8));
            return repositoryService.createDeployment()
                    .name(model.getName())
                    .addString(processName, new String(xmlBytes, StandardCharsets.UTF_8))
                    .deploy();
        } else {
            return null;
        }
    }

    private Model findModel(String modelId) {
        return repositoryService.getModel(modelId);
    }

    private byte[] findModelEditorSource(String modelId) {
        return repositoryService.getModelEditorSource(modelId);
    }

    public Collection<FlowElement> nodes(String modelId) throws IOException {
        Model model = findModel(modelId);
        if (model == null) {
            throw new WorkFlowException("要查看的模型不存在");
        }
        byte[] modelEditorSource = findModelEditorSource(model.getId());
        if (modelEditorSource == null || modelEditorSource.length == 0) {
            throw new WorkFlowException("要查看的模型源码不存在");
        }

        ObjectNode objectNode = (ObjectNode) new ObjectMapper().readTree(modelEditorSource);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(objectNode);
        Process process = bpmnModel.getProcesses().get(0);

        Collection<FlowElement> flowElements = process.getFlowElements();
        flowElements.forEach(e ->
                log.info("FlowElement name {} class {}", e.getName(), e.getClass().getName()));
        return flowElements;
    }


    public ObjectNode getEditorJson(String modelId) {
        ObjectNode modelNode = null;
        Model model = repositoryService.getModel(modelId);
        if (model != null) {
            try {
                if (StringUtils.isNotEmpty(model.getMetaInfo())) {
                    modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                } else {
                    modelNode = objectMapper.createObjectNode();
                    modelNode.put(MODEL_NAME, model.getName());
                }
                modelNode.put(MODEL_ID, model.getId());
                ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(
                        new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));
                modelNode.put("model", editorJsonNode);

            } catch (Exception e) {
                log.error("Error creating model JSON", e);
                throw new ActivitiException("Error creating model JSON", e);
            }
        }
        return modelNode;
    }

    public List<String> userTaskList(String modelId) throws IOException {
        Collection<FlowElement> nodes = nodes(modelId);
        FlowNode rootNode = this.findRootNode(nodes);
        List<FlowNode> nodeList = bfs(rootNode, nodes);
        return nodeList.stream().filter(each -> each instanceof Task).map(FlowNode::getName).collect(Collectors.toList());
    }

    private FlowNode findRootNode(Collection<FlowElement> nodes) {
        Optional<FlowElement> optionalRootElement = nodes.stream().filter(each -> {
            if (each instanceof FlowNode) {
                FlowNode flowNode = (FlowNode) each;
                return flowNode.getIncomingFlows().size() == 0;
            }
            return false;
        }).findFirst();

        FlowNode rootNode = null;
        if (optionalRootElement.isPresent()) {
            FlowElement flowElement = optionalRootElement.get();
            rootNode = (FlowNode) flowElement;
        }
        return rootNode;
    }

    /**
     * 对任务节点排序
     *
     * @param rootNode
     * @param nodes
     * @return
     */
    private List<FlowNode> bfs(final FlowNode rootNode, Collection<FlowElement> nodes) {

        Map<String, FlowElement> nodeMap = nodes.stream().filter(each -> each instanceof FlowNode).collect(Collectors.toMap(FlowElement::getId, Function.identity()));

        List<FlowNode> visitedList = new ArrayList<>();
        Queue<FlowNode> visitingQueue = new LinkedList<>();
        //放入根节点
        visitingQueue.offer(rootNode);
        //开始处理
        FlowNode vertex = visitingQueue.poll();
        while (vertex != null) {
            //获取顶点出度
            List<SequenceFlow> outgoingEdges = vertex.getOutgoingFlows();
            //将不在访问列表中 && 不再处理队列中的元素加入到队列
            for (SequenceFlow edge : outgoingEdges) {
                String targetRef = edge.getTargetRef();
                FlowNode targetNode = (FlowNode) nodeMap.get(targetRef);
                if (!visitedList.contains(targetNode) && !visitingQueue.contains(targetNode)) {
                    visitingQueue.offer(targetNode);
                }
            }
            //放入已经访问的列表
            visitedList.add(vertex);
            //当节点设置为最新的元素
            vertex = visitingQueue.poll();
        }
        return visitedList;
    }

    /**
     * 获取业务流程 某个任务节点对象
     *
     * @param processId
     * @param taskName
     * @return
     * @throws IOException
     */

    public FlowElement getFlowElement(Long processId, String taskName) throws IOException {
        String modelId = workFlowProcessDao.findById(processId).get().getModelId();
        Collection<FlowElement> nodes = nodes(modelId);
        List<FlowElement> list = nodes.stream()
                .filter(n -> n instanceof UserTask)
                .filter(n -> n.getName().equals(taskName))
                .collect(Collectors.toList());
        return list.isEmpty() ? list.get(0) : null;
    }

}
